Objective-C和JavaScript 数据交互

iOS7 之前

OC 调用 JS

iOS7以前,在OC中调用JavaScript的方式只有一种,就是通过UIWebView对象的stringByEvaluatingJavaScriptFromString:方法,可以实现的交互很少,还需要JS提供相应的方法,实现功能,常用代码如下:

1
NSString *title =[webView stringByEvaluatingJavaScriptFromString:@"document.title"];  //得到网页标题
JS 调用 OC

通过UIWebView的重定向,通过在UIWebView的代理函数webView:shouldStartLoadWithRequest:navigationType:来监听URL的变化,这个函数会在webview加载URL时回调,我们可以return YES来让webview继续加载内容,return NO来停止继续加载新的内容。要约定好几个虚假的URL(比如tool://goToHomePage),我们拿到定义好的URL之后做对应的Objective-C函数调用。

比如我们在JavaScript文件里添加点击响应的函数

function clickButton() {

      window.location = "tool://goToHomePage"

}

然后在webview的代理函数中监听,如果是事先定义好的虚假URL,就进行对应的处理

1
2
3
4
5
6
7
8
9
10
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = request.URL;
if ([[url scheme] isEqualToString:@"tool"] && [[url host] isEqualToString:@"goToHomePage"]) {
//添加跳转到HomePage的逻辑
[self jumpToHomePage];
return NO;
}
return YES;

}

可以看出这种做法非常笨重,效率低下,无法实现混合开发。接下来要说到这篇博客的重点,利用JavaScriptCore实现OCJS之间的深度交互。

JavaScriptCore

类概述

JavaScriptCore头文件中包括以下几类:

#import "JSContext.h"
#import "JSValue.h"
#import "JSManagedValue.h"
#import "JSVirtualMachine.h"
#import "JSExport.h"
JSContext

JavaScript的运行环境,你需要用JSContext来执行JavaScript代码。

JSValue

JavaScript实体,JSValue可以表示很多JavaScript原始类型例如boolean, integers, doubles,甚至包括对象,每个JSValue都是强引用一个JSContext

JSManagedValue

内存管理辅助对象,用来避免循环引用。本质上属于JSValue实例。

JSVirtualMachine

JS运行的虚拟机,有独立的堆空间和垃圾回收机制。

JSExport

协议,JS对象直接调用OC对象的属性及方法必须实现的协议。

OC 调用 JS

先加入JavaScriptCore的头文件。

#import <JavaScriptCore/JavaScriptCore.h>

实例代码,OC调用JS 实现 两个数字相加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 初始化对象
JSContext *context = [[JSContext alloc]init];
// JS 脚本
NSString *function = @" var sum = function(a,b){ "
" return a+b; "
" } ";
// 将脚本加入到context中
[context evaluateScript:function];

JSValue *jsFunction = context[@"sum"];
// 利用JSValue调用
JSValue *sum = [jsFunction callWithArguments:@[@1,@2]];
// 转换类型
NSInteger result = [sum toInt32];

NSLog(@"%@,%ld",sum,result); // sum = 3

JSValue 包括一系列方法用于访问其可能的值以保证有正确的 Foundation 类型,包括:

JavaScript Type JSValue method Objective-C Type
string toString NSString
number toNumber
toDouble
toInt32
toUInt32
NSNumber
double
int32_t
uint32_t
Date toDate NSDate
Array toArray NSArray
Object toDictionary NSDictionary
Object toObject
toObjectOfClass:
JS 调用 OC

JS调用OC有两个方法:BlockJSExport

Block

稍微复杂的运算给出一个数字,从1加到当前数字,将此函数在OC中实现,在JS中调用,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 初始化对象
JSContext *context = [[JSContext alloc]init];
// JS 脚本
NSString *function = @"var sum = function (){ return gauss(100);};";

[context evaluateScript:function];

context[@"gauss"] = ^(NSInteger a) {

NSInteger sum = 0;

for (NSInteger i = 0; i <= a; i ++) {

sum += i;
}

return sum;
};

JSValue *value = context[@"sum"];
JSValue *result = [value callWithArguments:@[]];

NSLog(@"%@",result); // result = 5050

我们可以得到JS执行上下文,利用JS语句得到相对应的DOM对象,运行OC中自定义的方法,完美。

JSExport

JSExport是一个协议,可以让原生类的属性或方法转化为JavaScript的属性或方法。需要我们自定义一个协议继承JSExport,然后我们在签署我们自定义的协议。代码如下:

@protocol MyExport <JSExport>

@end

@interface ViewController : UIViewController <MyExport>

@end

例如有如下JavaScript代码

1
2
3
4
5
6
7
8
function Person(name, age) {
this.name = name;
this.age = age;
}
var Persons = new Array;
function addPerson(p) {
Persons.push(p);
}

可以在Objective-C中把Person对象传递给addPerson函数

1
2
3
4
5
6
7
Person *aPerson = [[Person alloc] init];
aPerson.name = @"Jone";
aPerson.age = @25;
JSContext *context = [[JSContext alloc]init];
[context evaluateScript:JSString];
JSValue *function = context[@"addPerson"];
[function callWithArguments:@[aPerson]];
异常处理
1
2
3
context.exceptionHandler = ^(JSContext *context, JSValue *exception) {
NSLog(@"Error: %@", exception);
};
内存管理

Objective-C的内存管理机制是引用技术,JavaScript的内存管理机制是垃圾回收。由于每个JSValue都是强引用一个JSContext,可能会存在循环引用的情况。理解不深,不便多言。

参考资料